package com.onadake.domain.query;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;

import com.onadake.utils.DomainUtils;
import com.onadake.utils.ValidationUtils;

/**
 * This describes the records domain object. Records objects combines original limits,
 * total number of available records and selected chunk of objects.
 * 
 * @author radek.hecl
 *
 * @param <T> type of the queried objects
 */
public final class Records<T> {

    /**
     * Builder object
     * 
     * @param <T> type of the queried objects
     */
    public static class Builder<T> {

        /**
         * The total number of available records.
         */
        private Long numRecords;
        
        /**
         * Ordered list of the selected records.
         */
        private List<T> records = new ArrayList<>();
        
        /**
         * Sets total number of available records.
         * 
         * @param numRecords total number of available records
         * @return this instance
         */
        public Builder<T> setNumRecords(long numRecords) {
            this.numRecords = numRecords;
            return this;
        }

        /**
         * Sets records.
         * 
         * @param records records
         * @return this instance
         */
        public Builder<T> setRecords(List<T> records) {
            this.records = DomainUtils.softCopyList(records);
            return this;
        }
        
        /**
         * Add new record into list of records.
         * 
         * @param record record to add
         * @return this instance
         */
        public Builder<T> addRecord(T record) {
            records.add(record);
            return this;
        }

        /**
         * Builds the new object.
         * 
         * @return created object
         */
        public Records<T> build() {
            return new Records<>(this);
        }
        
    }
    
    /**
     * The total number of available records.
     */
    private Long numRecords;
    
    /**
     * Ordered list of the selected records.
     */
    private List<T> records;

    /**
     * Creates new instance.
     * 
     * @param builder builder object
     */
    public Records(Builder<T> builder) {
        numRecords = builder.numRecords;
        records = Collections.unmodifiableList(DomainUtils.softCopyList(builder.records));
        guardInvariants();
    }
    
    /**
     * Guards this object to be consistent. Throws exception if this is not the case.
     */
    private void guardInvariants() {
        ValidationUtils.guardNotNull(numRecords, "numRecords cannot be null");
        ValidationUtils.guardNotNegativeLong(numRecords, "numRecords cannot be negative");
        ValidationUtils.guardNotNullCollection(records, "records cannot be null or have any null element");
    }
    
    /**
     * Returns total number of available records.
     * 
     * @return total number of available records
     */
    public long getNumRecords() {
        return numRecords;
    }

    /**
     * Returns list of objects.
     * 
     * @return list of objects
     */
    public List<T> getRecords() {
        return records;
    }

    @Override
    public int hashCode() {
        return HashCodeBuilder.reflectionHashCode(this);
    }

    @Override
    public boolean equals(Object obj) {
        return EqualsBuilder.reflectionEquals(this, obj);
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this);
    }
}
